/*
 * Classic "Snake" game
 *
 * Object of game is to see how long you can grow the snake.
 * Use arrow keys to control direction of snake.
 * Hold SHIFT key to move faster.
 * Hit flashing target to grow snake by one segment.
 * Hitting wall, self or blocker will end game.
 * Game gets harder as snake becomes longer and longer.
 * Press ESCAPE to quit.
 *
 * Command line options:
 *	L=0 to 75		<= Set initial length of snake body (default 10)
 *	B=n				<= Place 'n' blockers in playing field (default 0)
 *
 * Dave Dunfield - Dec 2000
 *
 * Permission granted for personal (non-commercial) use only.
 *
 * Compile command: cc snake -fop
 */
#include <stdio.h>
#include <window.h>

// Dimensions of playing field
#define	XMAX	78				// Horizonal size
#define	YMAX	23				// Vertical size

// Marker characters placed in field to indicate content
// First four are also direction indicators for 'd' variable
#define	UP		1				// Snake segment, next is up
#define	RIGHT	2				// Snake segment, next is right
#define	DOWN	3				// Snake segment, next is down
#define	LEFT	4				// Snake segment, next is left
#define	HEAD	5				// Head of snake
#define	TARGET	6				// Target
#define	BLOCK	7				// Blocker

// PC characters to represent on-screen items
#define	Snake	0xFE			// Snake body
#define	Target1	0x02			// Target state 1
#define	Target2	0x01			// Target state 2
#define	Block	0xB1			// Blocker
// Table of snake head characters by direction
char Head[] = { 0x1E, 0x1E, 0x10, 0x1F, 0x11 };

extern unsigned RAND_SEED;

char help[] =
	{ "\nuse: SNAKE [L=starting_snake_length B=number_of_blockers]\n" };

main(int argc, char *argv[])
{
	unsigned i,					// General index counter
		x, y,					// position of head
		xl, yl,					// position of tail
		xt, yt;					// position of target
	unsigned char c, c1, *ptr;	// General variables
	static unsigned
		d=RIGHT,				// Direction of snake
		l=10,					// Length of snake
		b=0,					// Number of blockers
		f;						// Flag for flashing target
	static unsigned char
		field[XMAX][YMAX];		// Playing field

	// Parse command line arguments
	for(i=1; i < argc; ++i) {
		ptr = argv[i];
		switch((toupper(*ptr++) << 8) | toupper(*ptr++)) {
		case ('B'<<8)|'=' : b = atoi(ptr); continue;
		case ('L'<<8)|'=' : l = atoi(ptr); continue; }
		abort(help); }

	if(l > 75)
		abort("Maximum L=75\n");

	// Randomize the random number generatlr
	RAND_SEED = peekw(0x40, 0x6C);

	// Open the playing field window
	wopen(0,0,XMAX+2,YMAX+2,WSAVE|WCOPEN|WBOX1|NORMAL);
	wcursor_off();

	// Place the snake in it's starting position
	for(x=y=xl=yl=0; x < l; ++x) {
		field[x][y] = RIGHT;
		wgotoxy(x, y);
		wputc(Snake); }
	field[x][y] = HEAD;
	wgotoxy(x, y);
	wputc(*Head);

	// Place blockers
	for(i=0; i < b; ++i) {
		do {
			xt = random(XMAX);
			yt = random(YMAX); }
		while(field[xt][yt]);
		field[xt][yt] = BLOCK;
		wgotoxy(xt, yt);
		wputc(Block); }

	for(;;) {
		// Place a new target
		++l;
		do {
			xt = random(XMAX);
			yt = random(YMAX); }
		while(field[xt][yt]);
		field[xt][yt] = TARGET;

		// Move snake, responding to keys for direction, until we hit
		// the target in which case, loop exits, and outer loop replaces
		// the target.
		do {
			// Draw target and flash it
			wgotoxy(xt, yt);
			wputc((++f & 2) ? Target1 : Target2); 

			// If shift held, use shorter delay (move faster)
			if(peek(0x40, 0x17) & 0x03)
				delay((d & 1) ? 100 : 50);
			else
				delay((d & 1) ? 200 : 150);

			// Handle input keys
			switch(wtstc()) {
			case _KUA : d = UP; 	break;
			case _KRA : d = RIGHT;	break;
			case _KDA : d = DOWN;	break;
			case _KLA :	d = LEFT;	break;
			case 0x1B :	ptr = "ESCAPE KEY";
			quit:
				wclose();
				printf("Hit %s! - Final Snake length was %u.\n", ptr, l);
				return; }

			// Replace old head with a snake segment
			field[x][y] = d;
			wgotoxy(x, y);
			wputc(Snake);

			// Calculate new head position from direction
			switch(d) {
			case UP:	--y;	break;
			case RIGHT:	++x;	break;
			case DOWN:	++y;	break;
			case LEFT:	--x; }
			if((x >= XMAX) || (y >= YMAX)) {	// Hit a wall!
				ptr = "wall";
				goto quit; }

			// Check for field content at new head
			c = field[x][y];
			if(c != TARGET) {				// Did not hit the target
				if(c) {						// Hit something else!
					ptr = (c < TARGET) ? "Self" : "Blocker";
					goto quit; }
				// Remove tail of snake - only done when not target
				// When target, snake will grow by one segment
				wgotoxy(xl, yl);
				wputc(' ');
				c1 = field[xl][yl];
				field[xl][yl] = 0;
				switch(c1) {				// Locate new tail segment
				case UP:	--yl;		break;
				case RIGHT: ++xl;		break;
				case DOWN:	++yl;		break;
				case LEFT:	--xl; }
				if((xl >= XMAX) || (yl >= YMAX)) {
					wclose();
					printf("Last pos error!");
					return; } }

			// Draw new head
			wgotoxy(x, y);
			wputc(Head[d]);
			field[x][y] = HEAD; }
		while(c != TARGET); }
}
